<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title data-cn="语音识别转文字" data-en="Speech Recognition to Text"></title>
    <meta name="renderer" content="webkit" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="/static/layui/css/layui.css" rel="stylesheet" />
    <style>
      .preview-scroll {
        max-height: 450px;
        overflow: auto;
		color:#000;
      }
      .flex {
        display: flex;
        justify-content: center;
        align-items: center;
        margin-top: 10px;
        margin-left: 200px;
      }
      .flex-left {
        display: flex;
        align-items: center;
      }

      .my-1 {
        margin-top: 10px;
        margin-bottom: 10px;
      }

      .p-2 {
        padding: 15px;
      }

      .text-center {
        text-align: center;
      }

      .name {
        margin-right: 8px;
        width: 200px;
      }

      #upload {
        display: block;
        margin-bottom: 10px;
        border-style: solid;
        padding: 50px 30px;
      }

      .layui-form {
        margin: 15px auto;
      }

      .result-list {
        margin-top: 8px;
        margin-bottom: 8px;
        padding: 5px;
        border-bottom: 1px solid #f1f1f1;
      }

      .result-list .name {
        width: 150px;
        white-space: nowrap;
        text-overflow: ellipsis;
        text-align: left;
      }

      #content {
        width: 95%;
        min-width: 800px;
        max-width: 2000px;
        margin: 75px auto 50px;
      }

      .worktype {
        color: #999;
        font-size: 12px;
      }
      .text-res {
        margin-bottom: 15px;
      }
      .text-res-file {
        margin-bottom: 6px;
		font-size:18px;
		color:#000
      }
      .status {
        margin-left: 20px;
        width: 200px;
        white-space: break-spaces;
      }
	  .d-flex, .d-flex > div{
		display:flex;
		align-items:center;
		justify-content:center
	  }
	  .layui-form-label{
		width:auto;
		padding:5px;
	  }
	  .layui-btn-disabled{
		background:#666 !important
	  }
    </style>
  </head>
  <body>
    <div class="layui-layout layui-layout-admin">
      <div class="layui-header" style="background: #16b777;z-index:1">
        <div
          class="layui-logo layui-hide-xs"
          style="color: #fff"
          data-en="STT {{ version }}"
          data-cn="语音识别 {{ version }}"
        ></div>
        <!-- 头部区域（可配合layui 已有的水平导航） -->
        <ul class="layui-nav layui-layout-right">
          <!-- 移动端显示 -->
          <li id="checkupdate" class="layui-nav-item layui-hide">
            <a
              href="https://github.com/jianchang512/sts/releases"
              class="layui-font-red"
              target="_blank"
            ></a>
          </li>
          <li class="layui-nav-item layui-hide-xs">
            <a href="https://github.com/jianchang512/sts" target="_blank"
              >Github</a
            >
          </li>
          <li class="layui-nav-item layui-hide-xs">
            <a
              href="https://github.com/jianchang512/sts/issue"
              target="_blank"
              data-en="Post Isue"
              data-cn="遇到问题?"
            ></a>
          </li>
          <li class="layui-nav-item layui-hide-xs">
            <a href="https://discord.gg/TMCM2PfHzQ" target="_blank">Discord</a>
          </li>
          <li class="layui-nav-item layui-hide-xs">
            <a
              href="https://github.com/jianchang512/sts/releases/tag/0.0"
              target="_blank"
              data-cn="下载模型"
              data-en="Download models"
            ></a>
          </li>
        </ul>
      </div>
      <div id="content">
        <!-- 内容主体区域 -->
        <div class="layui-upload-drag layui-border-green" id="upload">
          <i class="layui-icon layui-icon-upload layui-font-green"></i>
          <div
            data-cn="点击上传，或将音频视频文件拖拽到此处(wav,mp3,flac,mp4,mov,mkv,avi,mpeg)"
            data-en="click to upload or drag video or audio to here(wav,mp3,flac,mp4,mov,mkv,avi,mpeg)"
          ></div>
          <div class="layui-hide my-1" id="preview">
            <div class="preview-scroll"></div>
          </div>
        </div>
        <form class="layui-form text-center layui-form-pane">
		<div class="d-flex">
          <div class="layui-form-item">
            <label
              class="layui-form-label"
              
              data-cn="发音语言"
              data-en="Pronunciation Language"
            ></label>
            <div class="">
              <select name="language">
                {% for name,val in lang_code.items() %}
                <option value="{{ val[0] }}">{{ name }}</option>
                {% endfor %}
              </select>
            </div>
          </div>
		  <div class="layui-form-item">
            <label
              class="layui-form-label"
              
              data-cn="选择模型"
              data-en="Select model"
            ></label>
            <div class="">
              <select name="model">
                {% for name in model_list %}
                <option value="{{name}}">{{name}}</option>
				{% endfor %}			
				
              </select>
            </div>
          </div>
          <div class="layui-form-item">
            <label
              class="layui-form-label"
              
              data-cn="返回格式"
              data-en="Return format"
            ></label>
            <div class="">
              <select id="data_type" name="data_type">
                <option
                  value="srt"
                  data-cn="srt字幕"
                  data-en="Subtitles srt"
                ></option>
                <option value="json">json</option>
                <option value="text" data-cn="纯文字" data-en="text"></option>
              </select>
            </div>
          </div>

          <!-- 自动导出开关 -->
          <div class="layui-form-item">
            <label
              class="layui-form-label"
              
              data-cn="自动导出"
              data-en="Auto Export"
            ></label>
            <div class="">
              <select id="switch" name="switch">
                <option value="off" data-cn="关闭" data-en="OFF"></option>
                <option value="on" data-cn="开启" data-en="ON"></option>
              </select>
            </div>
          </div>

          <!-- 是否独立导出 -->
          <div class="layui-form-item">
            <label
              class="layui-form-label"
              
              data-cn="独立导出"
              data-en="Individual export"
            ></label>
            <div class="">
              <select id="isIndependent" name="isIndependent">
                <option value="on" data-cn="是" data-en="ON"></option>
                <option value="off" data-cn="否" data-en="OFF"></option>
              </select>
            </div>
          </div>
	</div>
          <div
            class="layui-label-text layui-font-12 my-1 "
            data-en="The recognition of tiny to large-v3 models is becoming increasingly accurate, but it also consumes more resources. If you do not have the CUDA acceleration environment, do not choose large series models"
            data-cn="tiny到large-v3模型识别越来越精确，但也更消耗资源，如果不具备CUDA加速环境，请勿选用large系模型"
          ></div>
          

          <div class="layui-form-item layui-form-block">
            <input type="hidden" id="wav_name" name="wav_name" />
            <button
              type="submit"
              class="layui-btn layui-btn-danger"
              lay-submit
              lay-filter="submit"
              data-cn="立即识别（{{ devtype.upper()}}）"
              data-en="Start Separate ({{ devtype.upper()}})"
              id="submit-btn"
            >
              <i
                style="display: none"
                class="layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop"
              ></i>
            </button>
            <div
              class="layui-btn layui-btn-disabled"
              data-cn="导出文本"
              data-en="Export Text"
              id="export-btn"
            ></div>
          </div>
        </form>
		

        <div class="layui-card">
          <div class="layui-card-body text-contain" style="padding: 10px 0">
            <textarea
              placeholder-cn="识别结果在此显示"
              placeholder-en="Result list in here"
              class="layui-textarea"
              id="result"
              readonly
              cols="30"
              rows="10"
            ></textarea>
          </div>
        </div>
      </div>
    </div>
	
	<div style="text-align:center">
		<a data-cn="点击查看如何添加huggingface.co上的模型" data-en="Click to see how to add models on huggingface.co" style="text-align:center;font-size:12px;color:#777" href="https://pyvideotrans.com/stthuggingface" target="_blank"></a>
	</div>

    <script src="/static/layui/layui.js"></script>
    <script>
      let language = "{{ language }}";
      window.$ = layui.$;
      let intervalId = null;
      if (language === "zh") {
        $("[data-cn]").each(function () {
          $(this).html($(this).html() + $(this).attr("data-cn"));
        });
        $("[placeholder-cn]").each(function () {
          $(this).attr("placeholder", $(this).attr("placeholder-cn"));
        });
      } else {
        $("[data-en]").each(function () {
          $(this).html($(this).html() + $(this).attr("data-en"));
        });
        $("[placeholder-en]").each(function () {
          $(this).attr("placeholder", $(this).attr("placeholder-en"));
        });
      }
      /** 即将要处理的文件信息 */
      var pending_files = [];

      var processing = false;
      function get_file_el(file_name) {
        return $(`.flex[name="${file_name}"]`);
      }
      function set_status(file_name, status, color) {
        var file_element = get_file_el(file_name);
        file_element.find(".status").html(status).show().css("color", color);
      }

      var getprogress = function (file_name, field, star_time) {
        var file_element = get_file_el(file_name);
        var done = null;
        var handler = function () {
          $.post("/progressbar", field, function (res) {
            if (res.code !== 0) {
              done({
                error: res.msg,
                file_name,
              });
              set_status(file_name, res.msg, "#ff5722");
			  return
            }
            const percentage = `${(res["data"] * 100).toFixed(2)}%`;
            set_status(file_name, percentage, "#16b777");
            if (res["data"] >= 1) {
              var sec = +new Date() - star_time;
              set_status(
                file_name,
                `100%     ${(sec / 1000).toFixed(2)} sec`,
                "#16b777"
              );
              if (done) {
                done({
                  res,
                  file_name,
                });
              }
              return;
            }
            setTimeout(() => {
              handler();
            }, 500);
          }).fail(function () {
            setTimeout(() => {
              handler();
            }, 500);
          });
        };
        handler();
        return {
          done: function (cb) {
            done = cb;
          },
        };
      };

      function process(file, field) {
        return new Promise(function (resolve) {
          set_status(file.data, "0%", "#16b777");

			$.ajax({
				url:"/process", 
				method:"POST",
				data:field, 
				timeout:8640000000,
				success:function (res) {
					if (res.code !== 0) {
					  resolve({
						error: res.msg,
						file_name: file.data,
					  });
					  set_status(file.data, res.msg, "#ff5722");
					  return;
					}
					getprogress(file.data, field, +new Date()).done(function (res) {
					  resolve(res);
					});
				},
				error:function (msg) {
					set_status(file.data, msg.statusText, "#ff5722");
					resolve({
					  error: msg.statusText,
					  file_name: file.data,
					});
				}
			});
        });
      }

      function process_loading() {
        return {
          start: function () {
            processing = true;
            $("#submit-btn").addClass("layui-btn-disabled").find("i").show();
          },
          end: function () {
            processing = false;
            $("#submit-btn").removeClass("layui-btn-disabled").find("i").hide();
          },
        };
      }

      //JS
      layui.use(function () {
        var element = layui.element;
        var layer = layui.layer;

        var upload = layui.upload;
        let form = layui.form;
        // 渲染
        let layindex1 = null;
        upload.render({
          elem: "#upload",
          field: "audio",
          accept: "file",
          exts: "mp4|mp3|flac|wav|aac|m4a|avi|mkv|mpeg|mov",
          multiple: true,
          url: "/upload", // 实际使用时改成您自己的上传接口即可。
          choose: function () {
            pending_files = [];
          },
          before: function () {
            if (processing) {
              return false;
            }
			
			layindex1=layer.load();
          },
          done: function (res) {
            pending_files.push(res);
            //$('#wav_name').val(res.data);
            console.log(res);
          },
          allDone: function () {
			layer.close(layindex1)
            const file_element = pending_files.reduce((prev, cur) => {
              return `${prev}
                    <div class="flex" name="${cur.data}">
                        <span class="name">${cur.msg} ${cur.data || ""}</span>
                        <audio src="/static/tmp/${cur.data}" controls></audio>
                        <div class="status"></div>
                    </div>
                    `;
            }, "");
            $("#preview").removeClass("layui-hide").html(`
                    <hr>
                    <div class="preview-scroll">${file_element}</div>
                `);
			layer.msg(language === "zh"?'上传完成，可以点击开始识别了':'The upload is complete and you can click on Start Recognizing')
          },
        });
        // 导出
        function exportText(title, text) {
            var file_type = $("#data_type").val();
            var file_name = title.replace('wav', file_type.replace('text','txt'))
            var blob = new Blob([text], {
              type: "text/plain",
            });
            var url = URL.createObjectURL(blob);
            var aEl = document.createElement("a");
            aEl.href = url;
            aEl.target = "_blank";
            aEl.download = file_name;
            aEl.click();
            aEl.remove();
            URL.revokeObjectURL(url);
          }
        // 手动导出
        $("#export-btn").click(function () {
          if ($(this).hasClass("layui-btn-disabled")) {
            return;
          }
          var textElList = $(".text-res");
          if (!textElList.length) {
            layer.alert(
              language === "zh"
                ? "请先识别后再进行导出!"
                : "Please process first before exporting!",
              { title: false }
            );
            return;
          }
          // 是否独立导出
          if($("#isIndependent").val() === 'on') {
            var file_type = $("#data_type").val();
            [...textElList].forEach((el) => {
              var title = $(el).find(".text-res-file").text();
              var text = $(el).find("textarea").val();
              exportText(title, text)
            });
          }else {
            var exportInfos = [...textElList]
            .map(function (el) {
              var info = {
                title: $(el).find(".text-res-file").text(),
                text: $(el).find("textarea").val(),
              };
              return `${info.text}`;
            })
            .join("\n\n\n");
            var blob = new Blob([exportInfos], {
              type: 'text/plain'
            })
            var url = URL.createObjectURL(blob)
            var aEl = document.createElement('a')
            aEl.href = url
            aEl.target = '_blank'
            aEl.download = '全部音频文本.txt'
            aEl.click()
            aEl.remove()
            URL.revokeObjectURL(url)
          }

          
        });
        // 提交事件
        form.on("submit(submit)", function (data) {
          console.log('submit', data)
          if (processing) {
            return;
          }
          var field = data.field; // 获取表单全部字段值
          if (!pending_files.length) {
            layer.alert(
              language === "zh"
                ? "必须先上传要识别的音频或视频文件!"
                : "The file to be recognition must be uploaded first!",
              { title: false }
            );
            return false;
          }
		  if(field['model'].endsWith('.en') && field['language']!='en'){
			layer.alert(
              language === "zh"
                ? "en结尾的模型只可用于确定的英语语音识别!"
                : "Models ending in en can only be used for English speech recognition!",
              { title: false }
            );
            return false;
		  }
		  
		  
		  
          // 开始识别
          process_loading().start();
          // 按钮禁用
          $("#export-btn").addClass("layui-btn-disabled");
          $(".text-contain").html(`
          <textarea
              placeholder="${
                language === "zh" ? "识别结果在此显示" : "Result list in here"
              }"
              class="layui-textarea res-placeholder"
              id="result"
              readonly
              cols="30"
              rows="10"
            ></textarea>
          `);
          function getText(res) {
            try {
              if (typeof res === "string") {
                return res;
              }
              return JSON.stringify(res);
            } catch (e) {
              return res;
            }
          }
          // 批量处理文件
          Promise.allSettled(
            pending_files.map(function (file) {
              return process(file, {
                ...field,
                wav_name: file.data,
              }).then(function (res) {
                // 识别结果文本区域以及导出避免误操作
                $(".res-placeholder").hide();
                $("#export-btn").removeClass("layui-btn-disabled");
                // 文件名显示
                var texts_el = document.createElement("div");
                texts_el.className = "text-res";
                $(texts_el).html(
                  `<div class="text-res-file">${res.file_name}</div>
                    <textarea
                        name="${res.file_name}"
                        class="layui-textarea"
                        cols="30"
                        rows="10"
                    ></textarea>`
                );
                $(".text-contain").append(texts_el);
                $(texts_el).find("textarea").val(getText(res.res.result));
                // 自动导出
                if (field.switch === "on") {
                  exportText(res.file_name, getText(res.res.result))
                }
              });
            })
          ).finally(function () {
            // 识别按钮解禁
            process_loading().end();
          });
          return false; // 阻止默认 form 跳转
        });
        setTimeout(() => {
          $.get("/checkupdate", function (res) {
            if (res.code === 0 && res.msg) {
              $("#checkupdate").removeClass("layui-hide");
              $("#checkupdate a").text(res.msg);
            }
          });
        }, 60000);
      });

	
	
    </script>
  </body>
</html>
